#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/param.h>

#include "CALC_Stack.h"
#include "CALC_Proc.h"


static int g_iDebug;
static void Calc_Proc_Dbg(IN char c) {
  if (g_iDebug) {
    putchar(c);
  }

  return;
}


static int Cacl_Proc_GetOpPriority(IN char cOprator)
{
  int iPriority;

  iPriority = 0;

  /* We don't use any break bewteen legal operators here.
     Just put the higher priority operators before the lower ones */
  switch (cOprator) {
    case '*':
    case '/':
      ++iPriority;
    case '+':
    case '-':
      ++iPriority;
      break;
    default:
      iPriority = CALC_PROC_OPERATION_PRIORITY_LOWEST;
      break;
  }

  return iPriority;
}

/* transform a normal text Infix Expression 
   into a Postfix Expression in stack */
static int Calc_Proc_ParseExp(IN char *pcInfixExp,
                              OUT CALC_STACK_S *pstPostfixExpStack)
{
  CALC_ITEM_S stCalcItem;
  CALC_ITEM_DATA_TYPE CalcData;
  unsigned int uiOpratorRemainCnt;
  unsigned int uiInfixExpOffset;
  int iRet;
  int iOpratorCnt;
  int iPreOpratorPriority;
  int iCurOpratorPriority;
  int iBracketChkSum, iHaveContentInBracket;
  char acOpratorBuf[CALC_TEXT_EXP_ITEM_MAX];

  DBGASSERT(NULL != pcInfixExp);
  DBGASSERT(NULL != pstPostfixExpStack);

  uiOpratorRemainCnt = 0;
  uiInfixExpOffset = 0;
  bzero(&stCalcItem, sizeof(CALC_ITEM_S));
  iOpratorCnt = 0;
  iPreOpratorPriority = CALC_PROC_OPERATION_PRIORITY_LOWEST;
  iBracketChkSum = 0;
  iRet = ERR_SUCCESS;

  while ('\0' != pcInfixExp[uiInfixExpOffset]) {
    if (CALC_PROC_IsNum(pcInfixExp[uiInfixExpOffset])) {
      CalcData = (CALC_ITEM_DATA_TYPE)0;
      CALC_ITEM_SET_VALUE(stCalcItem, CALC_ITEM_TYPE_DATA, CalcData);
      iRet = Calc_StackPush(pstPostfixExpStack,&stCalcItem);
      
      while ((ERR_SUCCESS == iRet) &&
             (CALC_PROC_IsNum(pcInfixExp[uiInfixExpOffset]))) {
        Calc_StackPop(pstPostfixExpStack, &stCalcItem);
        CALC_ITEM_GET_VALUE(stCalcItem,
                            CALC_ITEM_TYPE_DATA,
                            CalcData);
        
        CALC_ITEM_SET_VALUE(stCalcItem, 
                            CALC_ITEM_TYPE_DATA, 
                            (10 * CalcData) + (pcInfixExp[uiInfixExpOffset] - '0'));
        Calc_StackPush(pstPostfixExpStack, &stCalcItem);

        Calc_Proc_Dbg(pcInfixExp[uiInfixExpOffset]);

        ++uiInfixExpOffset;
      }
    }
    else if (CALC_PROC_IsOp(pcInfixExp[uiInfixExpOffset])) {
      iCurOpratorPriority = Cacl_Proc_GetOpPriority(pcInfixExp[uiInfixExpOffset]);

      /* If current operator priority is NO HIGHER than previous one
         (NO need to WORRY about BRACKETS , as the open bracket have the lowest priority), 
         it is the same as there is a pair of bracket taking effect on the previous operator*/
      if (iCurOpratorPriority <= iPreOpratorPriority) {
        /* pop the previous operator from operator stack(array) and
           put it into the postfix expression stack */
        --iOpratorCnt;
        CALC_ITEM_SET_VALUE(stCalcItem, CALC_ITEM_TYPE_OP, acOpratorBuf[iOpratorCnt]);
        iRet = Calc_StackPush(pstPostfixExpStack,&stCalcItem);
        
        Calc_Proc_Dbg(acOpratorBuf[iOpratorCnt]);
      }
      
      acOpratorBuf[iOpratorCnt] = pcInfixExp[uiInfixExpOffset];
      ++iOpratorCnt;
      iPreOpratorPriority = iCurOpratorPriority;

      ++uiInfixExpOffset;
    }
    else if (CALC_PROC_IsOpenBkt(pcInfixExp[uiInfixExpOffset])) {
      ++iBracketChkSum;

      /* push open bracket into operation stack */      
      acOpratorBuf[iOpratorCnt] = pcInfixExp[uiInfixExpOffset];
      ++iOpratorCnt;
      iPreOpratorPriority = CALC_PROC_OPERATION_PRIORITY_LOWEST;

      ++uiInfixExpOffset;
    }
    else if (CALC_PROC_IsCloseBkt(pcInfixExp[uiInfixExpOffset])) {      

      --iBracketChkSum;
      if (0 > iBracketChkSum) {
        iRet = ERR_FAILED;

        break;
      }

      /* pop the previous operators from operator stack(array) and
         put it into the postfix expression stack until we meet open bracket.
         Specially, if no content between a pair of bracket, report ERROR*/ 
      iHaveContentInBracket = 0;
      do {
        --iOpratorCnt;
        if (CALC_PROC_IsOpenBkt(acOpratorBuf[iOpratorCnt])) {
          
          break;
        }
        else {
          iHaveContentInBracket = 1;
          CALC_ITEM_SET_VALUE(stCalcItem, CALC_ITEM_TYPE_OP, acOpratorBuf[iOpratorCnt]);
          iRet = Calc_StackPush(pstPostfixExpStack,&stCalcItem);
            
          Calc_Proc_Dbg(acOpratorBuf[iOpratorCnt]);
        }
      }while (0 != iOpratorCnt);

      if (!iHaveContentInBracket) {
        /* This can also be right, just leave it alone~ */
      }

      /* get the previous operator's priority */
      if (0 == iOpratorCnt) {
        iPreOpratorPriority = CALC_PROC_OPERATION_PRIORITY_LOWEST;
      }
      else {
        iPreOpratorPriority = Cacl_Proc_GetOpPriority(acOpratorBuf[iOpratorCnt - 1]);
      }

      ++uiInfixExpOffset;
    }
    else if (CALC_PROC_IsBlank(pcInfixExp[uiInfixExpOffset])) {
      ++uiInfixExpOffset;
    }
    else {
      iRet = ERR_FAILED;
    }    
  }

  while (0 != iOpratorCnt) {
    /* pop the previous operator from operator stack(array) and
       put it into the postfix expression stack */
    --iOpratorCnt;
    CALC_ITEM_SET_VALUE(stCalcItem, CALC_ITEM_TYPE_OP, acOpratorBuf[iOpratorCnt]);
    iRet |= Calc_StackPush(pstPostfixExpStack,&stCalcItem);
    
    Calc_Proc_Dbg(acOpratorBuf[iOpratorCnt]);
    
  }

  if (0 != iBracketChkSum) {
    iRet = ERR_FAILED;
  }

  return iRet;
}

static int Calc_Proc_CalcTwoPram(IN char cOpType, INOUT CALC_STACK_S *pstResultStack) {
  CALC_ITEM_S stFirstItem, stNextItem;
  CALC_ITEM_DATA_TYPE stFirstData, stNextData;
  int iRet;

  DBGASSERT(NULL != pstResultStack);

  iRet = Calc_StackPop(pstResultStack,&stNextItem);
  iRet |= Calc_StackPop(pstResultStack,&stFirstItem);

  if ((ERR_SUCCESS != iRet) ||
      (CALC_ITEM_TYPE_DATA != stNextItem.uiType) ||
      (CALC_ITEM_TYPE_DATA != stFirstItem.uiType)) {
    return ERR_FAILED;
  }
  
  CALC_ITEM_GET_VALUE(stNextItem, CALC_ITEM_TYPE_DATA, stNextData);
  CALC_ITEM_GET_VALUE(stFirstItem, CALC_ITEM_TYPE_DATA, stFirstData);
  
  switch (cOpType) {
    case '+':
      CALC_ITEM_SET_VALUE(stFirstItem, CALC_ITEM_TYPE_DATA, stFirstData + stNextData);
      break;
    case '-':
      CALC_ITEM_SET_VALUE(stFirstItem, CALC_ITEM_TYPE_DATA, stFirstData - stNextData);
      break;
    case '*':
      CALC_ITEM_SET_VALUE(stFirstItem, CALC_ITEM_TYPE_DATA, stFirstData * stNextData);
      break;
    case '/':
      if ((-0.000001 < stNextData) && (0.000001 > stNextData)) {
        iRet = ERR_FAILED;
      } else {
        CALC_ITEM_SET_VALUE(stFirstItem, CALC_ITEM_TYPE_DATA, stFirstData / stNextData);
      }
      break;
    default:
      iRet = ERR_FAILED;
      break;
  }

  if (ERR_SUCCESS == iRet) {
    iRet = Calc_StackPush(pstResultStack,&stFirstItem);
  }

  return iRet;
}
    

static int Calc_Proc_GetAndCheckResult(IN CALC_STACK_S *pstPostfixExpStack,
                                       OUT CALC_ITEM_S *pstResult)
{
  CALC_STACK_S stResultStack;
  CALC_ITEM_S stResult;
  CALC_ITEM_S *pstExpItem;
  unsigned int uiExpItemTotal, i;
  int iRet;

  DBGASSERT(NULL != pstResult);
  DBGASSERT(NULL != pstPostfixExpStack);

  iRet = Calc_StackInit(CALC_TEXT_EXP_ITEM_MAX, &stResultStack);

  if (ERR_SUCCESS != iRet) {
    return ERR_FAILED;
  }

  uiExpItemTotal = Calc_StackGetItemCount(pstPostfixExpStack);
  pstExpItem = pstPostfixExpStack->pstStack;
  
  for (i = 0;
       (i < uiExpItemTotal) && (ERR_SUCCESS == iRet);
       ++i) {
    switch (pstExpItem[i].uiType) {
      case CALC_ITEM_TYPE_DATA:
        iRet = Calc_StackPush(&stResultStack, &pstExpItem[i]);
        
        break;
      case CALC_ITEM_TYPE_OP: 
        iRet = Calc_Proc_CalcTwoPram(pstExpItem[i].aucValue[0], &stResultStack);

        break;
      default:
        iRet = ERR_FAILED;

        break;
    }
  }

  /* If calced successfully, then get and check result */
  if (ERR_SUCCESS == iRet) {
    iRet = Calc_StackPop(&stResultStack,&stResult);
  }
  
  if ((!Calc_IsStackEmpty(&stResultStack)) ||
      (CALC_ITEM_TYPE_DATA != stResult.uiType)){
    iRet = ERR_FAILED;
  }

  Calc_StackDestroy(&stResultStack);

  if (ERR_SUCCESS == iRet) {
    memcpy(pstResult, &stResult, sizeof(CALC_ITEM_S));
  }

  return iRet;
}

int CALC_Proc_CalcTextExp(IN char *pcInfixExp,
                          OUT CALC_ITEM_S *pstResult)
{
  CALC_STACK_S stPostfixExpStack;
  CALC_ITEM_S  stResult;
  int iRet;

  /* Init calc stack, one for store expression, the other for store result */
  iRet = Calc_StackInit(CALC_TEXT_EXP_ITEM_MAX, &stPostfixExpStack);

  if (0 != iRet) {
    return ERR_FAILED;
  }

  /* Parse text infix expression into postfix expression and store it into stack */
  iRet = Calc_Proc_ParseExp(pcInfixExp, &stPostfixExpStack);
  if (0 != iRet) {
    Calc_StackDestroy(&stPostfixExpStack);
    
    return ERR_FAILED;
  }

  /* Calc the final value of postfix expression */ 
  iRet = Calc_Proc_GetAndCheckResult(&stPostfixExpStack, &stResult);
  if (ERR_SUCCESS == iRet) {
    memcpy(pstResult, &stResult, sizeof(CALC_ITEM_S));
  }

  Calc_StackDestroy(&stPostfixExpStack);

  return iRet;
}

int main( 
    IN int Argc,
    IN char **Argv
)
{
  CALC_ITEM_S stItem;
  CALC_ITEM_DATA_TYPE stResult;
  int iRet;
  char szTextExp[CALC_TEXT_EXP_ITEM_MAX];

  printf("Wlecome! Add \"debug\" comment to show postfix expression.\n");
  g_iDebug = 0;
  if ((2 == Argc) && (0 == strcasecmp(Argv[1], "debug"))) {
    g_iDebug = 1;
  }
  
  do {
    printf("Type text expression to calculate, type \"exit\" to exit:\n");
    scanf("%s", szTextExp);

    if (0 == strcasecmp(szTextExp, "exit")) {
      
      break;
    }
    
    iRet = CALC_Proc_CalcTextExp(szTextExp, &stItem);
    
    Calc_Proc_Dbg('\n');
    
    if (ERR_SUCCESS == iRet) {    
      CALC_ITEM_GET_VALUE(stItem, CALC_ITEM_TYPE_DATA, stResult);
      printf("%s = %lf\n", szTextExp, stResult);
    }
    else {
      printf("u got something wrong...\n");
    }
  } while(1);

  return 0;
}

